﻿using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using CWM.VeiwTree;
using UnityEditor.IMGUI.Controls;
using UnityEditor.AnimatedValues;

namespace CWM.Skinn
{

    public class SkinnBlendshapeWizard : ScriptableWizard
    {
        public static SkinnBlendshapeWizard shapeWizard = null;
        public static Vector2 scroll = Vector2.zero;

        [ContextMenuItem("Reset", "ResetFunctions")]
        [Multiline(4)]
        public string functions = ShapeFilter.GetDefaultFunctions;

        [HideInInspector] public string[] blendshapes;
        [HideInInspector] public TextAsset textFilter;

        private AnimBool showShapes;
        private ShapeTreeGUI shapeUI = new ShapeTreeGUI();

        private void ResetFunctions()
        {
            functions = ShapeFilter.GetDefaultFunctions;
            Repaint();
        }

        public static void ShowWizard(string[] blendshapes)
        {
            shapeWizard = DisplayWizard<SkinnBlendshapeWizard>("Blend-shape Filter");
            if (!SkinnEx.IsNullOrEmpty(blendshapes)) shapeWizard.blendshapes = blendshapes;
        }

        private void OnEnable()
        {
            ResetFunctions();
            scroll = Vector2.zero;

            if (showShapes == null) showShapes = new AnimBool(false);
            showShapes.valueChanged.RemoveListener(Repaint);
            showShapes.valueChanged.AddListener(Repaint);

            Undo.undoRedoPerformed -= shapeUI.OnUndoRedoPerformed;
            Undo.undoRedoPerformed += shapeUI.OnUndoRedoPerformed;
        }

        private void OnDisable() { shapeWizard = null; }

        private void OnWizardUpdate() { }

        private void OnGUI()
        {
            if (shapeWizard && shapeWizard != this) { this.Close(); return; }

            base.DrawWizardGUI();

            var rectWidth = GUILayoutUtility.GetLastRect().width;

            scroll = EditorGUILayout.BeginScrollView(scroll);

            EditorGUILayout.LabelField("");

            showShapes.target = EditorGUILayout.Foldout(showShapes.target, new GUIContent("Blend-shapes", "blend-shapes projection filter."));

            if (showShapes.faded > 0)
            {
                using (new EditorGUILayout.FadeGroupScope(showShapes.faded))
                {
                    Rect rect = GUILayoutUtility.GetRect(rectWidth, shapeUI.GetPropertyHeight() + 20);
                    shapeUI.OnGUI(rect, blendshapes, GetInstanceID().ToString());
                }
            }

            EditorGUILayout.LabelField("");

            EditorGUILayout.EndScrollView();

            EditorGUILayout.LabelField("");


            var skinnedMeshes = new List<SkinnedMeshRenderer>();
            foreach (var item in Selection.gameObjects) { skinnedMeshes.AddRange(item.GetValidSkinnedMeshes(true, false)); }

            EditorGUILayout.LabelField(string.Format("{0} Skinned Meshes Selected", skinnedMeshes.Count), EditorStyles.centeredGreyMiniLabel);



            if (GUILayout.Button(new GUIContent("Get Blend-shapes", "Adds all the blend-shapes from the current GameObject(s)."), GUI.skin.button))
            {
                var meshes = new List<Mesh>(skinnedMeshes.Count);
                foreach (var item in skinnedMeshes) { if (item.sharedMesh) meshes.Add(item.GetSharedMesh()); }
                blendshapes = meshes.ToArray().GetBlendshapeNames().ToArray();
                Repaint();
                return;
            }

            EditorGUILayout.LabelField("");

            using (new EditorGUILayout.HorizontalScope())
            {
                if (GUILayout.Button(new GUIContent( "Create TextAsset Filter", "Create a simple text file filter from the current settings."), GUI.skin.button))
                {
                    string path = EditorUtility.SaveFilePanelInProject("Export TextFilter", "", "txt", "TextAsset");
                    if (!string.IsNullOrEmpty(path))
                    {
                        if (!path.EndsWith(".txt")) Debug.LogWarning("file path should end with .txt");
                        if (System.IO.File.Exists(path)) AssetDatabase.DeleteAsset(path);
                        var elements = SkinnInternalAsset.Asset ? SkinnInternalAsset.Asset.blendshapeFilter.elements : new List<BoneElement>();
                        ShapeFilter.Write(path, elements, functions, blendshapes);
                        AssetDatabase.Refresh();
                        textFilter = (TextAsset)AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset));
                    }
                }

                textFilter = (TextAsset)EditorGUILayout.ObjectField(textFilter, typeof(TextAsset), true);

                using (new EditorGUI.DisabledGroupScope(!textFilter || skinnedMeshes.Count < 1))
                {
                    if (GUILayout.Button(new GUIContent("Apply TextAsset Filter", "Applies to the current selection."), GUI.skin.button))
                    {

                        skinnedMeshes = new List<SkinnedMeshRenderer>();
                        foreach (var item in Selection.gameObjects) { skinnedMeshes.AddRange(item.GetValidSkinnedMeshes()); }

                        foreach (var item in skinnedMeshes)
                        {
                            var mesh = item.GetSharedMesh();
                            if (mesh.blendShapeCount < 1) continue;

                            string errors;
                            ShapeFilter filter;

                            if(ShapeFilter.Read(out filter, out errors, textFilter))
                            {
                                var clone = mesh.Clone() as Mesh;
                                clone.ClearBlendShapes();
                                clone.CopyBlendshapes(mesh, null, null, filter);

                                Undo.RecordObject(item, "Apply Text Filter");
                                item.sharedMesh = clone;
                            }

                            if(!string.IsNullOrEmpty(errors)) Debug.LogWarning(errors);
                        }
                    }
                }
            }


            using (new EditorGUI.DisabledGroupScope(skinnedMeshes.Count < 1))
            {
                if (GUILayout.Button("Apply Filter", GUI.skin.button))
                {
                    skinnedMeshes = new List<SkinnedMeshRenderer>();
                    foreach (var item in Selection.gameObjects) { skinnedMeshes.AddRange(item.GetValidSkinnedMeshes(true, false)); }

                    foreach (var item in skinnedMeshes)
                    {
                        var mesh = item.GetSharedMesh();
                        if (!mesh) continue;
                        if (mesh.blendShapeCount < 1) continue;

                        var elements = SkinnInternalAsset.Asset ? SkinnInternalAsset.Asset.blendshapeFilter.elements : new List<BoneElement>();
                        string errors;
                        ShapeFilter filter;
                        if (ShapeFilter.Read(out filter, out errors, blendshapes, functions, elements))
                        {
                            var clone = mesh.Clone() as Mesh;
                            clone.ClearBlendShapes();
                            clone.CopyBlendshapes(mesh, null, null, filter);

                            Undo.RecordObject(item, "Apply Filter");
                            item.sharedMesh = clone;
                        }
                        if (!string.IsNullOrEmpty(errors)) Debug.LogWarning(errors);

                    }
                }

            }

            EditorGUILayout.LabelField("");
        }

        private void OnWizardCreate()
        {


        }

        private void OnWizardOtherButton()
        {

        }

        private class ShapeTreeGUI
        {
            private BoneTreeView treeview;
            private SearchField searchFeild;

            private ElementFilter filter;

            private string kSession = "";
            const string kSessionStateKeyPrefix = "ShapeTree45gdsg325326";
            const float topToolbarHeight = 20f;
            const float spacing = 2f;
            const float startingIndent = 30f;

            public float GetPropertyHeight()
            {
                float lineHeight = EditorGUIUtility.singleLineHeight;
                if (!treeview) return lineHeight;
                float totalHeight = treeview.totalHeight + topToolbarHeight + lineHeight;
                return totalHeight;
            }

            public int renameId = -1;

            private void SetupTreeVeiw(ElementFilter filter, string stateHash)
            {
                var bones = filter.elements;
                string hashName = stateHash;
                if (treeview && hashName == kSession) return;
                if (treeview && !string.IsNullOrEmpty(kSession)) SessionState.SetString(kSession, JsonUtility.ToJson(treeview.state));

                kSession = hashName;

                var treeViewState = new TreeViewState();
                var jsonState = SessionState.GetString(kSession, "");
                if (!string.IsNullOrEmpty(jsonState)) JsonUtility.FromJsonOverwrite(jsonState, treeViewState);
                var treeModel = new TreeModel<BoneElement>(bones);
                treeview = new BoneTreeView(treeViewState, treeModel);

                treeview.OnBeforeDroppingDraggedItems += OnBeforeDroppingDraggedItems;
                treeview.OnTreeChanged += OnTreeChanged;
                treeview.OnSelectionChanged += OnTreeSelectionChanged;

                searchFeild = new SearchField();
                searchFeild.downOrUpArrowKeyPressed += treeview.SetFocusAndEnsureSelectedItem;

                treeview.OnElementRow -= RowGUI;
                treeview.OnElementRow += RowGUI;
                SkinnGizmos.OnSceneGUI -= SceneGUI;
                SkinnGizmos.OnSceneGUI += SceneGUI;

                treeview.Reload();
                treeview.ExpandAll();
            }

            private void SceneGUI()
            {
            }

            private void RowGUI(BoneTreeView.ElementRow args)
            {
                if (!filter) return;

                if (renameId == args.item.id)
                {
                    renameId = -1;
                    treeview.BeginRename(args.item);
                    return;
                }

                if (args.isRenaming) return;


                Event evt = Event.current;
                int id = args.item.id;
                Rect rowRect = args.rowRect.OffsetX(args.contentIndent);
                if (evt.type == EventType.ContextClick && rowRect.Contains(evt.mousePosition))
                {
                    treeview.DoSelectionClick(args.item, true);
                    ShowContextMenu();
                }

                var bones = filter.elements; if (SkinnEx.IsNullOrEmpty(bones)) return;
                int index = filter.elements.GetBoneTreeElementIndex(id, true); if (index < 0) return;
                var bone = bones[index];

                string info = "(enabled)";
                if (id == bone.orginalIndex) info = "(not available)";
                else if (!bone.enabled) info = "(disabled)";

                GUIContent contentLabel = new GUIContent(bone.Name, "right click for options.");
                GUIContent contentDesc = new GUIContent(info);
                GUIStyle style = bone.enabled ? EditorStyles.label : new GUIStyle(EditorStyles.label) { fontStyle = FontStyle.Italic };
                EditorGUI.LabelField(rowRect.GetRectX(), contentLabel, style);
                EditorGUI.LabelField(rowRect.GetRectX(false).GetRectX(false), contentDesc, style);

            }

            public void OnGUI(Rect position, string[] shapes, string stateHash = "")
            {
                if (!SkinnInternalAsset.Asset || SkinnEx.IsNullOrEmpty(shapes)) return;
                ElementFilter elementFilter = SkinnInternalAsset.Asset.blendshapeFilter;


                float labelHeight = EditorGUIUtility.singleLineHeight;
                EditorGUI.LabelField(position.SetHeight(labelHeight).OffsetX(30), "Shapes", EditorStyles.miniLabel);


                filter = elementFilter;

                if (!filter.IsValidShapes(shapes))
                {
                    filter.GenerateAsShapes(shapes);
                    filter.assetID = shapes.GetHashCode().ToString() + stateHash;
                    treeview = null;

                    if (treeview)
                    {
                        treeview.treeModel.SetData(filter.elements);
                        treeview.Reload();
                        treeview.ExpandAll();
                    }
                }

                SetupTreeVeiw(filter, GetStateHash(filter.assetID));

                Rect rect = position.OffsetX(startingIndent, true);
                Rect searchRect = rect.OffsetY(labelHeight - 1f).SetHeight(topToolbarHeight).GetRectX(false);
                Rect treeRect = rect.OffsetY(labelHeight + topToolbarHeight, true);
                treeview.searchString = searchFeild.OnGUI(searchRect, treeview.searchString);

                treeview.OnGUI(treeRect);
            }

            private static string GetStateHash(string id)
            {
                return kSessionStateKeyPrefix + id;
            }

            public void OnUndoRedoPerformed()
            {
                if (!treeview || !filter) return;

                treeview.treeModel.SetData(filter.elements);
                treeview.Reload();
            }

            private void OnTreeChanged()
            {
                if (!SkinnInternalAsset.Asset) return;
                Undo.RecordObject(SkinnInternalAsset.Asset, "Shape Wizard");
            }

            private void OnTreeSelectionChanged()
            {
                if (!treeview) return;
            }

            private void OnBeforeDroppingDraggedItems(IList<TreeViewItem> draggedRows)
            {
                if (!SkinnInternalAsset.Asset) return;
                Undo.RecordObject(SkinnInternalAsset.Asset, "Shape Wizard");
            }

            public void ShowContextMenu()
            {
                GenericMenu menu = new GenericMenu();
                for (int i = 0; i < ContextEnableDisableCommand.Commands.Length; i++)
                {
                    string commandID = ContextEnableDisableCommand.Commands[i];
                    if (commandID.EndsWith("/")) { menu.AddSeparator(commandID); continue; }
                    menu.AddItem(new GUIContent(commandID), false, ContextMenuSelected, commandID);
                }
                menu.ShowAsContext();
            }

            private static class ContextEnableDisableCommand
            {
                public const string depth = "";
                public static readonly string[] Commands = new string[]
                {
                     depth + "Rename",
                     depth + "/",
                     depth + "Disable",  depth + "DisableAll",
                     depth + "/",
                     depth + "Enable",  depth + "EnableAll"
                };
            }

            private void ContextMenuSelected(object contextCommandID)
            {
                string id;
                try { id = contextCommandID as string; } catch { id = ""; }
                if (!filter || !treeview) return;
                switch (id)
                {
                    case ContextEnableDisableCommand.depth + "Rename":
                        {
                            var select = treeview.GetSelection();
                            renameId = select[0];
                            return;
                        }
                    case ContextEnableDisableCommand.depth + "Disable":
                        {
                            var bones = GetSelectedBones();
                            for (int i = 0; i < bones.Count; i++) bones[i].enabled = false;
                            break;
                        }
                    case ContextEnableDisableCommand.depth + "DisableChildren":
                        {
                            var bones = GetSelectedBones();
                            for (int i = 0; i < bones.Count; i++)
                            {
                                var children = bones[i].GetChildern(false);
                                for (int ii = 0; ii < children.Count; ii++) children[ii].enabled = false;
                            }
                        }
                        break;
                    case ContextEnableDisableCommand.depth + "DisableAll":
                        {
                            var bones = filter.elements;
                            for (int i = 0; i < bones.Count; i++) bones[i].enabled = false;
                        }
                        break;
                    case ContextEnableDisableCommand.depth + "Enable":
                        {
                            var bones = GetSelectedBones();
                            for (int i = 0; i < bones.Count; i++) bones[i].enabled = true;
                            break;
                        }
                    case ContextEnableDisableCommand.depth + "EnableChildren":
                        {
                            var bones = GetSelectedBones();
                            for (int i = 0; i < bones.Count; i++)
                            {
                                var children = bones[i].GetChildern(false);
                                for (int ii = 0; ii < children.Count; ii++) children[ii].enabled = true;
                            }
                        }
                        break;
                    case ContextEnableDisableCommand.depth + "EnableAll":
                        {
                            var bones = filter.elements;
                            for (int i = 0; i < bones.Count; i++) bones[i].enabled = true;
                        }
                        break;
                    default: break;
                }

                OnTreeChanged();
            }

            private List<BoneElement> GetSelectedBones()
            {
                List<BoneElement> selection = new List<BoneElement>();
                if (!treeview || !treeview.HasSelection()) return selection;
                IList<int> selectionHashes = treeview.GetSelection();
                for (int i = 0; i < selectionHashes.Count; i++)
                { var bone = filter.elements.GetBoneTreeElement(selectionHashes[i], true); if (bone) selection.Add(bone); }
                return selection;
            }
        }
    }
}